﻿#region BSD License
/* 
Copyright (c) 2011, NETFx
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list 
  of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this 
  list of conditions and the following disclaimer in the documentation and/or other 
  materials provided with the distribution.

* Neither the name of Clarius Consulting nor the names of its contributors may be 
  used to endorse or promote products derived from this software without specific 
  prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;
using System.Reflection.Emit;

/// <devdoc>
/// Provides additional behavior to the Event Sourcing package DomainObject partial class 
/// to allow auto wiring of event handlers by using 
/// </devdoc>
///	<nuget id="netfx-Patterns.EventSourcing.AutoWire" />
partial class DomainObject<TObjectId, TBaseEvent>
{
	private static readonly MethodInfo handlesGeneric = typeof(DomainObject<TObjectId, TBaseEvent>)
		.GetMethod("Handles", BindingFlags.Instance | BindingFlags.NonPublic);

	private static Dictionary<Type, Action<DomainObject<TObjectId, TBaseEvent>>> initializersMap =
		new Dictionary<Type, Action<DomainObject<TObjectId, TBaseEvent>>>();

	/// <summary>
	/// As an alternative to explicitly calling <see cref="Handles{TEvent}"/> 
	/// from your aggregate root constructor to specify the handlers 
	/// for each type of event, this method will use reflection (once per type)
	/// to discover the handlers for events by looking for void methods with 
	/// a single parameter inheriting from <typeparamref name="TBaseEvent"/>
	/// and registering that with the <see cref="Handles{TEvent}"/> method like 
	/// you would do manually.
	/// </summary>
	/// <devdoc>
	/// This code will hurt your eyes, but it's tunned for performance so that 
	/// it's as close to your own manually configured handlers as possible. 
	/// This is the reason why this was created as a separate optional 
	/// package and not part of the very simple aggregate root for plain 
	/// event sourcing. 
	/// Perf. is about 8-15x slower than manual code, but still 5x faster than 
	/// cached compiled lambdas and orders of magnitude than plain reflection.
	/// </devdoc>
	protected void AutoWireHandlers()
	{
		var initializer = initializersMap.GetOrAdd(this.GetType(), type => BuildInitializer());

		initializer(this);
	}

	///	<devdoc>
	///	This code was tweaked from the base generated by 
	///	the Reflection.Emit language adding to Reflector 7, 
	///	which is the only practical way of using this way 
	///	of codegen. Install from http://kzu.to/nN0iPu
	/// </devdoc>
	private Action<DomainObject<TObjectId, TBaseEvent>> BuildInitializer()
	{
		var initialize = new DynamicMethod("Initialize", typeof(void), new[] { typeof(DomainObject<TObjectId, TBaseEvent>) }, true);
		var @this = initialize.DefineParameter(1, ParameterAttributes.None, "this");
		var gen = initialize.GetILGenerator();
		var typed = gen.DeclareLocal(this.GetType());

		var methods = from method in this.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
					  let parameters = method.GetParameters()
					  // Select all methods with one parameter that inherits from TBaseEvent
					  where parameters.Length == 1 && typeof(TBaseEvent).IsAssignableFrom(parameters[0].ParameterType) &&
							// Skip this base class own virtual event handler.
							method.DeclaringType != typeof(DomainObject<TObjectId, TBaseEvent>)
					  select method;

		foreach (var method in methods)
		{
			var eventType = method.GetParameters()[0].ParameterType;
			var handlerActionCtor = typeof(Action<>).MakeGenericType(eventType).GetConstructors().First();
			var handlesConcrete = handlesGeneric.MakeGenericMethod(eventType);

			gen.Emit(OpCodes.Nop);
			gen.Emit(OpCodes.Ldarg_0);
			gen.Emit(OpCodes.Castclass, this.GetType());
			gen.Emit(OpCodes.Stloc_0);
			gen.Emit(OpCodes.Ldloc_0);
			gen.Emit(OpCodes.Ldloc_0);

			gen.Emit(OpCodes.Ldftn, method);
			gen.Emit(OpCodes.Newobj, handlerActionCtor);
			gen.Emit(OpCodes.Callvirt, handlesConcrete);
			gen.Emit(OpCodes.Nop);
		}

		gen.Emit(OpCodes.Nop);
		gen.Emit(OpCodes.Ret);

		return (Action<DomainObject<TObjectId, TBaseEvent>>)initialize.CreateDelegate(typeof(Action<DomainObject<TObjectId, TBaseEvent>>));
	}
}